﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;

namespace Isuka.Excel
{
    public sealed class XRange
        : ExcelObject
        , IXGrid
        , IEquatable<XRange>
    {
        readonly object lockobject = new object();
        readonly XWorksheet worksheet;
        XRange rows;
        XRange columns;

        internal XRange(XWorksheet sheet, object range)
            : base(sheet.Application, range)
        {
            Contract.Requires(sheet != null);
            Contract.Requires(range != null);

            this.worksheet = sheet;
        }

        #region Indexer

        /// <summary>
        /// 指定したセル範囲のオフセット値で指定される範囲を表す Range オブジェクトを返します。
        /// 数えて、アクセスするセルのインデックス番号を指定します。
        /// </summary>
        /// <param name="index">
        /// 左から右に、次に上から下に数えて、アクセスするセルのインデックス番号を指定します。
        /// 0がセル範囲の開始を表します。
        /// </param>
        /// <returns></returns>
        public XRange this[int index]
        {
            get
            {
                Contract.Ensures(Contract.Result<XRange>() != null);
                VerifyNotDisposed();

                var result = new XRange(worksheet, Object[index + 1]);
                Contract.Assume(result != null);
                return result;
            }
        }

        /// <summary>
        /// 指定したセル範囲を表す Range オブジェクトを返します。
        /// R1C1形式で指定した場合、指定したセル範囲のオフセット値で指定される範囲を表す Range オブジェクトを返します。
        /// </summary>
        /// <param name="beginAddress">範囲の開始アドレス</param>
        /// <param name="beginAddress">範囲の終了アドレス</param>
        /// <returns></returns>
        public XRange this[string beginAddress, string endAddress = null]
        {
            get
            {
                Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(beginAddress));
                Contract.Ensures(Contract.Result<XRange>() != null);
                VerifyNotDisposed();

                if (string.IsNullOrEmpty(endAddress))
                {
                    object range = Object[beginAddress];
                    Contract.Assume(range != null);
                    return new XRange(worksheet, range);
                }
                else
                {
                    object range = Object[beginAddress, endAddress];
                    Contract.Assume(range != null);
                    return new XRange(worksheet, range);
                }
            }
        }

        #endregion

        /// <summary>
        /// セル範囲の絶対アドレスを取得する
        /// </summary>
        public string Address
        {
            get
            {
                VerifyNotDisposed();
                return GetAddress();
            }
        }

        /// <summary>
        /// セル範囲の列位置を取得する
        /// </summary>
        public int Column
        {
            get
            {
                VerifyNotDisposed();
                return Object.Column - 1;
            }
        }

        /// <summary>
        /// セル範囲に含まれるセル（範囲）の個数を取得する
        /// </summary>
        public int Count
        {
            get
            {
                VerifyNotDisposed();
                return Object.Count;
            }
        }

        /// <summary>
        /// セル範囲を選択状態にする
        /// </summary>
        public void DoSelect()
        {
            VerifyNotDisposed();
            Object.Select();
        }

        /// <summary>
        /// セル範囲が含まれる列全体を取得
        /// </summary>
        public XRange EntireColumn
        {
            get
            {
                VerifyNotDisposed();
                object range = Object.EntireColumn;
                Contract.Assume(range != null);
                return new XRange(worksheet, range);
            }
        }

        /// <summary>
        /// セル範囲が含まれる行全体を取得
        /// </summary>
        public XRange EntireRow
        {
            get
            {
                VerifyNotDisposed();
                object range = Object.EntrieRow;
                Contract.Assume(range != null);
                return new XRange(worksheet, range);
            }
        }

        /// <summary>
        /// セル範囲の外部アドレスを取得する
        /// </summary>
        public string FullAddress
        {
            get
            {
                return GetAddress(External: true);
            }
        }

        /// <summary>
        /// セル範囲のアドレスを取得する
        /// </summary>
        /// <param name="RowAbsolute">絶対行で取得するかどうか</param>
        /// <param name="ColumnAbsolute">絶対列で取得するかどうか</param>
        /// <param name="External">外部アドレスを取得するかどうか</param>
        /// <returns>セル範囲のアドレス</returns>
        public string GetAddress(bool RowAbsolute = true, bool ColumnAbsolute = true, bool External = false)
        {
            VerifyNotDisposed();
            return Object.Address(RowAbsolute: RowAbsolute, ColumnAbsolute: ColumnAbsolute, External: External);
        }

        /// <summary>
        /// セル範囲の行位置
        /// </summary>
        public int Row
        {
            get
            {
                VerifyNotDisposed();
                return Object.Row - 1;
            }
        }

        /// <summary>
        /// セル範囲の計算式
        /// </summary>
        public dynamic Formula
        {
            get
            {
                VerifyNotDisposed();
                return Object.Formula;
            }
            set
            {
                VerifyNotDisposed();
                Object.Formula = value;
            }
        }

        /// <summary>
        /// セル範囲の値
        /// </summary>
        public dynamic Value
        {
            get
            {
                VerifyNotDisposed();
                return Object.Value;
            }
            set
            {
                VerifyNotDisposed();
                Object.Value = value;
            }
        }

        /// <summary>
        /// セル範囲を含むワークシート
        /// </summary>
        public XWorksheet Worksheet
        {
            get
            {
                return worksheet;
            }
        }

        #region IXGrid メンバー

        public XRange this[int rowIndex, int columnIndex]
        {
            get
            {
                VerifyNotDisposed();
                object range = Object[rowIndex + 1, columnIndex + 1];
                Contract.Assume(range != null);
                return new XRange(worksheet, range);
            }
        }

        public XRange this[int rowIndex, string columnIndex]
        {
            get
            {
                VerifyNotDisposed();
                object range = Object[rowIndex + 1, columnIndex];
                Contract.Assume(range != null);
                return new XRange(worksheet, range);
            }
        }

        public XRange Columns
        {
            get
            {
                VerifyNotDisposed();
                if (columns == null)
                {
                    object result = Object.Columns;
                    Contract.Assume(result != null);
                    columns = new XRange(worksheet, result);
                }
                return columns;
            }
        }

        public XRange Rows
        {
            get
            {
                VerifyNotDisposed();
                if (rows == null)
                {
                    object result = Object.Rows;
                    Contract.Assume(result != null);
                    rows = new XRange(worksheet, result);
                }
                return rows;
            }
        }

        #endregion

        #region IEnumerable<XRange> メンバー

        public IEnumerator<XRange> GetEnumerator()
        {
            VerifyNotDisposed();
            return GetEnumeratorIterator();
        }

        IEnumerator<XRange> GetEnumeratorIterator()
        {
            foreach (var item in Object)
            {
                yield return new XRange(worksheet, item);
            }
        }

        #endregion

        #region IEnumerable メンバー

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        #endregion

        #region IEquatable<XRange> メンバー

        public bool Equals(XRange other)
        {
            if ((object)other == null)
            {
                return false;
            }
            else
            {
                return FullAddress == other.FullAddress;
            }
        }

        // override object.Equals
        public override bool Equals(object obj)
        {
            // TODO: write your implementation of Equals() here
            return Equals(obj as XRange);
        }

        // override object.GetHashCode
        public override int GetHashCode()
        {
            // TODO: write your implementation of GetHashCode() here
            return FullAddress.GetHashCode();
        }

        #endregion

        #region ContractInvariantMethod

        [ContractInvariantMethod]
        [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Required for code contracts.")]
        [DebuggerStepThrough]
        private void ObjectInvariant()
        {
            Contract.Invariant(worksheet != null);
        }

        #endregion

    }
}
